import string
from random import choice, sample, shuffle


class CharsetTypeError(Exception):
    def __init__(self, message):
        super().__init__(message)


class CharNotEnoughError(Exception):
    def __init__(self):
        super().__init__("给定字符集的可用字符数量不足")


def ran(length: int, flag: int = 15, chars: list = None, digits: list = None, lower: list = None, upper: list = None,
        symbols: list = None, uniform=True, unique=True):
    """
    根据给定条件生成随机字符串
    :param length: 生成字符串的长度
    :param flag: 字符类型标志, 1表示使用数字, 2表示使用小写字母, 4表示使用大写字母, 8表示使用特殊字符, 默认为15, 表示1+2+4+8
    :param chars: 指定字符集, 如果不为None, 则只会使用chars中包含的字符生成字符串, 字符标志及其他字符集的设定失效
    :param digits: 数字字符集, 字符类型标志flag包含数字, 且chars为None时有效, 生成字符串包含的数字只会在digits中挑选, 为None时使用任意数字
    :param lower: 小写字母字符集, 字符类型标志flag包含小写字符, 且chars为None时有效, 生成字符串包含的小写字母只会在lower中挑选, 为None时使用任意小写字母
    :param upper: 大写字母字符集, 字符类型标志flag包含大写字符, 且chars为None时有效, 生成字符串包含的大写字母只会在upper中挑选, 为None时使用任意大写字母
    :param symbols: 特殊字符字符集, 字符类型标志flag包含特殊字符, 且chars为None时有效, 生成字符串包含的特殊字符只会在symbols中挑选, 为None时使用任意特殊字符
    :param uniform: 手动指定chars时无效, 为True时均匀使用各字符集的字符, 为False时可能使用了某种字符类型, 但生成的字符串不包含此类型的字符
    :param unique: 为True时生成的字符串中不含重复字符
    :return: 返回生成的字符串
    """
    if not chars:
        """如果未给定字符集, 则生成字符集"""
        chars = []
        # 如果未设置数字标志, 则数字字符集设置为空列表
        # 如果设置了数字标志但未给定数字字符集, 则生成数字字符集
        # 如果设置了数字标志且给定了数字字符集, 则对给定的数字字符集去重
        digits = list(set(''.join(digits))) if digits else list(string.digits) if flag % 2 else []
        # 如果未设置小写字母标志, 则小写字母字符集设置为空列表
        # 如果设置了小写字母标志但未给定小写字母字符集, 则生成小写字母字符集
        # 如果设置了小写字母标志且给定了小写字母字符集, 则对给定的小写字母字符集去重
        lower = list(set(''.join(lower))) if lower else list(string.ascii_lowercase) if (flag >> 1) % 2 else []
        # 如果未设置大写字母标志, 则大写字母字符集设置为空列表
        # 如果设置了大写字母标志但未给定大写字母字符集, 则生成大写字母字符集
        # 如果设置了大写字母标志且给定了大写字母字符集, 则对给定的大写字母字符集去重
        upper = list(set(''.join(upper))) if upper else list(string.ascii_uppercase) if (flag >> 2) % 2 else []
        # 如果未设置符号标志, 则符号字符集设置为空列表
        # 如果设置了符号标志但未给定符号字符集, 则生成符号字符集
        # 如果设置了符号标志且给定了符号字符集, 则对给定的符号字符集去重
        symbols = list(set(''.join(symbols))) if symbols else list(string.punctuation) if (flag >> 3) % 2 else []
        # 合并生成的字符集
        if uniform:
            """如果设置了uniform, 则将各字符集作为整体保存到字符集列表"""
            if digits:
                chars.append(digits)
            if lower:
                chars.append(lower)
            if upper:
                chars.append(upper)
            if symbols:
                chars.append(symbols)
        else:
            """否则直接生成字符集列表"""
            chars = digits + lower + upper + symbols
    else:
        """如果给定字符集, 则通过给定的字符集生成结果"""
        # 如果给定字符集包含非字符元素, 则报错
        for cha in chars:
            if not isinstance(cha, str):
                raise CharsetTypeError("字符集元素必须是字符类型")
        if uniform:
            chars = [chars]
        else:
            # 如果给定字符集包含字符串, 会将字符串拆散并去重
            chars = list(set(''.join(chars)))

    chars_count = 0
    '''可用字符总数'''
    for s in chars:
        chars_count += len(s)
    if uniform:
        """如果设置了uniform"""
        # 计算字符类型的个数
        type_count = len(chars)
        '''字符类型个数'''
        result_list = []
        '''临时存储结果字符列表'''
        if unique:
            """设置了uniform且设置了unique"""
            if chars_count < length:
                raise CharNotEnoughError
            # 计算每种字符的个数
            res_length = length
            '''还需生成的字符个数'''
            count = []
            '''每种类型的字符个数'''
            base = res_length // type_count
            '''每种类型字符的最少个数'''
            res_type_count = type_count
            '''未分配的类型个数'''
            while res_length > 0:
                # 如果剩余的字符个数除以字符的类型个数有余数, 则当前类型的的个数在最少个数的基础上+1
                if res_length % res_type_count:
                    count.append(base + 1)
                    res_length -= (base + 1)
                else:
                    count.append(base)
                    res_length -= base
                res_type_count -= 1
            # 生成结果字符串的字符列表
            for i in range(min(type_count, length)):
                result_list.extend(sample(chars[i], count[i]))
        else:
            """设置了uniform但未设置unique"""
            # 循环添加各字符集的字符
            for i in range(length):
                result_list.append(choice(chars[i % type_count]))
        # 结果乱序
        shuffle(result_list)
        result = ''.join(result_list)
    else:
        """未设置uniform"""
        if unique:
            if chars_count < length:
                raise CharNotEnoughError
        # noinspection PyTypeChecker
        result = ''.join(sample(chars, length)) if unique else ''.join([choice(chars) for _ in range(length)])
    return result


class Creator:
    def __init__(self):
        self._flag = 15
        '''字符类型标志'''
        self._chars = None
        '''全字符集'''
        self._digits = None
        '''数字字符集'''
        self._lower = None
        '''小写字母字符集'''
        self._upper = None
        '''大写字母字符集'''
        self._symbols = None
        '''特殊符号字符集'''
        self._uniform = True
        '''均匀标志
        
        为True时, 均匀使用各字符集的字符生成密码'''
        self._unique = True
        '''唯一标志
        
        为True时, 生成密码的各字符均不重复'''

    @property
    def digits_flag(self):
        return bool(self._flag & 1)

    @digits_flag.setter
    def digits_flag(self, value: bool):
        self._flag = self._flag | 1 if value else self._flag & 0b1110

    @property
    def lower_flag(self):
        return bool(self._flag & 0b10)

    @lower_flag.setter
    def lower_flag(self, value: bool):
        self._flag = self._flag | 0b10 if value else self._flag & 0b1101

    @property
    def upper_flag(self):
        return bool(self._flag & 0b100)

    @upper_flag.setter
    def upper_flag(self, value: bool):
        self._flag = self._flag | 0b100 if value else self._flag & 0b1011

    @property
    def symbols_flag(self):
        return bool(self._flag & 0b1000)

    @symbols_flag.setter
    def symbols_flag(self, value: bool):
        self._flag = self._flag | 0b1000 if value else self._flag & 0b111

    @property
    def chars(self):
        return ''.join(self._chars) if self._chars else ''

    @chars.setter
    def chars(self, value: str):
        for i in value:
            if i in string.ascii_letters or i in string.digits or i in string.punctuation:
                continue
            else:
                raise CharsetTypeError('包含非法字符')
        self._chars = []
        [self._chars.append(i) for i in list(value) if i not in self._chars]

    @chars.deleter
    def chars(self):
        self._chars = []

    @property
    def digits(self):
        return ''.join(self._digits) if self._digits else ''

    @digits.setter
    def digits(self, value: str):
        if value.isdigit():
            self._digits = []
            [self._digits.append(i) for i in value if i not in self._digits]
        else:
            raise CharsetTypeError('包含非数字的字符')

    @digits.deleter
    def digits(self):
        self._digits = []

    @property
    def lower(self):
        return ''.join(self._lower) if self._lower else ''

    @lower.setter
    def lower(self, value: str):
        if value.islower():
            self._lower = []
            [self._lower.append(i) for i in value if i not in self._lower]
        else:
            raise CharsetTypeError('包含非小写字母的字符')

    @lower.deleter
    def lower(self):
        self._lower = []

    @property
    def upper(self):
        return ''.join(self._upper) if self._upper else ''

    @upper.setter
    def upper(self, value: str):
        if value.isupper():
            self._upper = []
            [self._upper.append(i) for i in value if i not in self._upper]
        else:
            raise CharsetTypeError('包含非大写字母的字符')

    @upper.deleter
    def upper(self):
        self._upper = []

    @property
    def symbols(self):
        return ''.join(self._symbols) if self._symbols else ''

    @symbols.setter
    def symbols(self, value: str):
        for i in value:
            if i not in string.punctuation:
                raise CharsetTypeError('包含非特殊字符')
        else:
            self._symbols = []
            [self._symbols.append(i) for i in value if i not in self._symbols]

    @symbols.deleter
    def symbols(self):
        self._symbols = []

    @property
    def uniform(self):
        return self._uniform

    @uniform.setter
    def uniform(self, value: bool):
        self._uniform = value

    @property
    def unique(self):
        return self._unique

    @unique.setter
    def unique(self, value):
        self._unique = value

    class CharsetTypeError(Exception):
        pass

    def main_menu(self):
        print("随机密码生成器".center(50, '='))
        print('状态'.center(50, '-'))
        print('\t数字\t小写\t大写\t符号\t均匀\t唯一')
        print(f'标志\t{'是' if self.digits_flag else '否'}\t{'是' if self.lower_flag else '否'}\t'
              f'{'是' if self.upper_flag else '否'}\t{'是' if self.symbols_flag else '否'}\t'
              f'{'是' if self.uniform else '否'}\t{'是' if self.unique else '否'}')
        print(''.center(50, '-'))
        print(f'字符集\t\t{self.chars if self.chars else "不指定"}')
        print(f'数字\t\t{self.digits if self.digits else "全部"}')
        print(f'小写字母\t{self.lower if self.lower else "全部"}')
        print(f'大写字母\t{self.upper if self.upper else "全部"}')
        print(f'符号\t\t{self.symbols if self.symbols else "全部"}')
        print('选项'.center(50, '-'))
        print('1.切换标志')
        print('2.设置字符集')
        print('3.生成')
        print('0.退出')
        print(''.center(50, '-'))
        return input('请选择: ')

    def flag_menu(self):
        print('标志菜单'.center(50, '-'))
        print('1.数字标志')
        print('2.小写字母标志')
        print('3.大写字母标志')
        print('4.特殊字符标志')
        print('5.均匀标志(均匀使用所有字符集)')
        print('6.唯一标志(字符无重复)')
        print('0.返回')
        print('请选择'.center(50, '-'))
        flag_choice = input()
        match flag_choice:
            case '1':
                self.digits_flag = not self.digits_flag
            case '2':
                self.lower_flag = not self.lower_flag
            case '3':
                self.upper_flag = not self.upper_flag
            case '4':
                self.symbols_flag = not self.symbols_flag
            case '5':
                self.uniform = not self.uniform
            case '6':
                self.unique = not self.unique
            case '0':
                return
            case _:
                input('未定义的选项(回车继续)')

    def create(self):
        length = input('请输入密码长度: ')
        if length.isdigit():
            length = int(length)
        else:
            input('类型有误(回车继续)')
        count = input('请输入生成个数: ')
        if count.isdigit():
            count = int(count)
        else:
            input('类型有误(回车继续)')
        for _ in range(count):
            print(ran(length, self._flag, self._chars, self._digits, self._lower, self._upper, self._symbols,
                      self.uniform,
                      self.unique))
        input('(回车继续)')

    def charset_menu(self):
        print('字符集菜单'.center(50, '-'))
        print('1.设置数字字符集')
        print('2.设置小写字母字符集')
        print('3.设置大写字母字符集')
        print('4.设置特殊字符字符集')
        print('5.设置全字符集(如果设置此项, 其他设置全部失效)')
        print('6.清除设置')
        print('0.返回')
        print('请选择'.center(50, '-'))
        charset_choice = input()
        match charset_choice:
            case '1':
                s = input('请输入可用的数字: ')
                try:
                    self.digits = s
                    input('设置成功(回车继续)')
                except CharsetTypeError as e:
                    print(e, end='')
                    input(', 设置失败(回车继续)')
            case '2':
                s = input('请输入可用的小写字母: ')
                try:
                    self.lower = s
                    input('设置成功(回车继续)')
                except CharsetTypeError as e:
                    print(e, end='')
                    input(', 设置失败(回车继续)')
            case '3':
                s = input('请输入可用的大写字母: ')
                try:
                    self.upper = s
                    input('设置成功(回车继续)')
                except CharsetTypeError as e:
                    print(e, end='')
                    input(', 设置失败(回车继续)')
            case '4':
                s = input('请输入可用的特殊字符: ')
                try:
                    self.symbols = s
                    input('设置成功(回车继续)')
                except CharsetTypeError as e:
                    print(e, end='')
                    input(', 设置失败(回车继续)')
            case '5':
                s = input('请输入可用字符: ')
                try:
                    self.chars = s
                    input('设置成功(回车继续)')
                except CharsetTypeError as e:
                    print(e, end='')
                    input(', 设置失败(回车继续)')
            case '6':
                print('1.清除数字字符集')
                print('2.清除小写字母字符集')
                print('3.清除大写字母字符集')
                print('4.清除特殊字符字符集')
                print('5.清除全字符集')
                print('6.清除全部')
                print('请选择'.center(50, '-'))
                cho = input()
                match cho:
                    case '1':
                        del self.digits
                    case '2':
                        del self.lower
                    case '3':
                        del self.upper
                    case '4':
                        del self.symbols
                    case '5':
                        del self.chars
                    case '6':
                        del self.digits
                        del self.lower
                        del self.upper
                        del self.symbols
                        del self.chars
                    case _:
                        input('未定义的选项(回车继续)')
            case '0':
                return
            case _:
                input('未定义的选项(回车继续)')

    def start(self):
        while 1:
            option = self.main_menu()
            match option:
                case '1':
                    self.flag_menu()
                case '2':
                    self.charset_menu()
                case '3':
                    try:
                        self.create()
                    except CharNotEnoughError as e:
                        print(e, end='')
                        input('(回车继续)')
                case '0':
                    return
                case _:
                    input('未定义的选项(回车继续)')


if __name__ == '__main__':
    c = Creator()
    c.start()
